package edu.kpi.pzks2.simulator.processing;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import edu.kpi.pzks2.graph.Node;
import edu.kpi.pzks2.simulator.core.EndTaskListener;
import edu.kpi.pzks2.simulator.core.router.Router;
import edu.kpi.pzks2.simulator.links.Message;
import edu.kpi.pzks2.simulator.links.SimplexChannel;
import edu.kpi.pzks2.simulator.results.ProcHistoryBean;
import edu.kpi.pzks2.simulator.results.TimeLine;

public class Processor {
    private final int productivity;
    private final int logicChannelCount;
    private final Router router;
    private final EndTaskListener endTaskListener;
    private final Node node;
    private final TimeLine history;
    
    private final Set<Integer> receivedMessages;
    private final List<Message> sendingMessages;
    
    private Map<Processor, SimplexChannel> channels;
    
    private Task currentTask;
    private int leftTime;
    
    public Processor(
            final int productivity,
            final int logicChannelCount,
            final Router router,
            final EndTaskListener endTaskListener,
            final Node node) {
        this.productivity = productivity;
        this.logicChannelCount = logicChannelCount;
        this.endTaskListener = endTaskListener;
        this.router = router;
        this.node = node;
        
        channels = new HashMap<Processor, SimplexChannel>();
        receivedMessages = new HashSet<Integer>();
        sendingMessages = new LinkedList<Message>();
        history = new TimeLine(node);
    }

    public void makeStep() {
        // Task processing
        if (currentTask != null && currentTask.isReady(receivedMessages)) {
            history.addBean(new ProcHistoryBean(currentTask));
            
            leftTime -= productivity;
            
            if (leftTime <= 0) {
                endTaskListener.taskEnded(this, currentTask);
                receivedMessages.add(currentTask.getId());
                currentTask = null;
            }
        } else {
            if (currentTask != null) {
                history.addBean(new ProcHistoryBean(currentTask, true));
            } else {
                history.addBean(null);                
            }
        }
        
        // Message sending
        if (!sendingMessages.isEmpty()) {
            final Iterator<Message> sendMsgIter = sendingMessages.iterator();
            
            while (sendMsgIter.hasNext()) {
                final Message msg = sendMsgIter.next();
                final Processor procTo = router.route(this, msg.getProcTo());
                final SimplexChannel channel = channels.get(procTo);
                if (channel.sendMessage(msg, procTo)) { 
                    sendMsgIter.remove();
                }
            }
        }
    }

    public int getFreeChannelCount() {
        int busyChanelsCount = 0;
        
        for (SimplexChannel chan : channels.values()) {
            if (chan.isBusy()) busyChanelsCount++;
        }
        
        return logicChannelCount - busyChanelsCount;
    }
    
    public boolean isBusy() {
        return leftTime > 0;
    }
    
    public void setTask(final Task task) {
        this.currentTask = task;
        leftTime = task.getWeight();
    }
    
    /**
     * Receives message = adds message to the received messages set or to the redirected msg list (if this proc isn't 
     * destination).
     * @param msg
     */
    public void receiveMessage(final Message msg) {
        if (msg.getProcTo() == this) {
            receivedMessages.add(msg.getResultOfTask());
        } else {
            sendingMessages.add(msg);
        }
        
    }
    
    /**
     * Connects the processor and the channel.
     * @param channel the added simplex channel
     */
    public void addChannel(final SimplexChannel channel) {
        channels.put(channel.getLinkedProc(), channel);
    }
    
    /**
     * @return the processor id
     */
    public int getProcId() {
        return node.getId();
    }
    
    /**
     * @return corresponding to this processor node
     */
    public Node getNode() {
        return node;
    }
    
    public TimeLine getHistory() {
        return history;
    }
    
    @Override
    public String toString() {
        return "Proc" + node.getId() + 
                (currentTask != null
                ? " Task" + currentTask.getId() + " " + leftTime
                : "empty");
    }
}
